﻿using Huirui.Cavan.Domain.MainBoundedContext.SystemModule;
using Huirui.Cavan.Domain.Seedwork;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Huirui.Cavan.Domain.MainBoundedContext.StockModule;
using Huirui.Cavan.Core.Extension;

namespace Huirui.Cavan.Domain.MainBoundedContext.SalesModule
{
    public class Seller
    {
        private Employee _employee;

        public Seller(Employee employee)
        {
            this._employee = employee;
        }

        public BoolGenericResult<Customer> ContractCustomer(string id, SystemIdentifier.AgentType agentType, string name, SystemIdentifier.CustomerNature nature, string deliveryAddress, string contact, string telephone, string mobile, string fax, string email, Area area, string address, string addressEnglish, string deliveryAddress1, string deliveryAddress2, string postCode, SystemIdentifier.PayType payType, decimal discountRate, int payDay, decimal creditLimit, string taxIdentificationNumber, SystemIdentifier.InvoiceType invoiceType, string bank, string bankAccount, string remark, WareHouse wareHouse)
        {
            BoolGenericResult<Customer> result = new BoolGenericResult<Customer>();

            Customer customer = SystemModuleFactory.CreateCustomer(id, agentType, name, nature, deliveryAddress, contact, telephone, mobile, fax, email, area,
                address, addressEnglish, deliveryAddress1, deliveryAddress2, postCode, payType, discountRate, payDay, creditLimit, taxIdentificationNumber,
                invoiceType, bank, bankAccount, remark, this._employee.UserAccount.Id, this._employee.UserAccount.Role, this._employee.Corporation, wareHouse);

            result.Object = customer;

            return result;
        }

        public BoolGenericResult<Customer> UpdateCustomer(Customer oldCustomer, SystemIdentifier.AgentType agentType, string name, SystemIdentifier.CustomerNature nature, string deliveryAddress, string contact, string telephone, string mobile, string fax, string email, Area area, string address, string addressEnglish, string deliveryAddress1, string deliveryAddress2, string postCode, SystemIdentifier.PayType payType, decimal discountRate, int payDay, decimal creditLimit, string taxIdentificationNumber, SystemIdentifier.InvoiceType invoiceType, string bank, string bankAccount, string remark, WareHouse wareHouse)
        {
            BoolGenericResult<Customer> result = new BoolGenericResult<Customer>();

            oldCustomer.Address = address;
            oldCustomer.AddressEnglish = addressEnglish;
            oldCustomer.AgentType = agentType;
            oldCustomer.Area = area;
            oldCustomer.Bank = bank;
            oldCustomer.BankAccount = bankAccount;
            oldCustomer.Contact = contact;
            oldCustomer.CreditLimit = creditLimit;
            oldCustomer.DeliveryAddress = deliveryAddress;
            oldCustomer.DeliveryAddress1 = deliveryAddress1;
            oldCustomer.DeliveryAddress2 = deliveryAddress2;
            oldCustomer.DiscountRate = discountRate;
            oldCustomer.EditUser = this._employee.UserAccount.Id;
            oldCustomer.Email = email;
            oldCustomer.Fax = fax;
            oldCustomer.InvoiceType = invoiceType;
            oldCustomer.Mobile = mobile;
            oldCustomer.ModifyDate = DateTime.Now;
            oldCustomer.Name = name;
            oldCustomer.PayDay = payDay;
            oldCustomer.PayType = payType;
            oldCustomer.PostCode = postCode;
            oldCustomer.Remark = remark;
            oldCustomer.TaxIdentificationNumber = taxIdentificationNumber;
            oldCustomer.Telephone = telephone;
            oldCustomer.Nature = nature;
            oldCustomer.WareHouse = wareHouse;
            result.Object = oldCustomer;

            return result;
        }

        public BoolGenericResult<SalesOrder> AddSalesOrder(string id, DateTime saleDate, DateTime deliveryDate, string currency, decimal currencyRate, string customerOrderNumber, string deliveryAddress,
            SalesIdentifier.CheckType checkType, string remark, decimal discountRate, decimal discountAmount, int receiptType, int receiptCondition, int payDay, DateTime deadDay, Customer customer, Employee saleEmployee, WareHouse wareHouse, Project project, IList<SalesOrderDetail> salesOrderDetails)
        {
            BoolGenericResult<SalesOrder> result = new BoolGenericResult<SalesOrder>();

            SalesOrder salesOrder = SalesModuleFactory.CreateSalesOrder(id, saleDate, deliveryDate, currency, currencyRate, customerOrderNumber,
                deliveryAddress, checkType, remark, discountRate, discountAmount, receiptType, receiptCondition, payDay, DateTime.Now.AddYears(1), SalesIdentifier.StockOutStatus.UnOut, DateTime.Now, DateTime.Now,
                this._employee.UserAccount.Id, this._employee.Corporation, customer, saleEmployee, wareHouse, project, SystemIdentifier.CheckedStatus.UnChecked);

            foreach (SalesOrderDetail sod in salesOrderDetails)
            {
                decimal salesAmount = sod.Number * sod.UnitCost;
                decimal sodDiscountAmount = salesAmount * (sod.DiscountRate/100);
                decimal wholeAmount = salesAmount / (1 + (sod.TaxRate / 100));
                decimal taxAmount = salesAmount - wholeAmount;        
                SalesOrderDetail newsod = SalesModuleFactory.CreateSalesOrderDetail(sod.Number, sod.UnitCost, sod.DiscountRate, sodDiscountAmount,
                    salesAmount, wholeAmount, sod.TaxRate, taxAmount, sod.Description, DateTime.Now, DateTime.Now, this._employee.UserAccount.Id,
                    salesOrder, sod.Stock, sod.WareHouse);

                salesOrder.SalesOrderDetails.Add(newsod);
            }

            result.Object = salesOrder;

            return result;
        }

        public BoolGenericResult<SalesOrder> UpdateSalesOrder(SalesOrder oldSalesOrder, DateTime saleDate, DateTime deliveryDate, string currency, decimal currencyRate, string customerOrderNumber, string deliveryAddress,
            SalesIdentifier.CheckType checkType, string remark, decimal discountRate, decimal discountAmount, int receiptType, int receiptCondition, int payDay, DateTime deadDay, Customer customer, Employee saleEmployee, WareHouse wareHouse, Project project, IList<SalesOrderDetail> salesOrderDetails)
        {
            BoolGenericResult<SalesOrder> result = new BoolGenericResult<SalesOrder>();

            oldSalesOrder.CheckType = checkType;
            oldSalesOrder.Currency = currency;
            oldSalesOrder.CurrencyRate = currencyRate;
            oldSalesOrder.Customer = customer;
            oldSalesOrder.CustomerOrderCode = customerOrderNumber;
            oldSalesOrder.DeadDay = deadDay;
            oldSalesOrder.DeliveryAddress = remark;
            oldSalesOrder.DeliveryDate = deliveryDate;
            oldSalesOrder.DiscountAmount = discountAmount;
            oldSalesOrder.DiscountRate = discountRate;
            oldSalesOrder.PayDay = payDay;
            oldSalesOrder.Project = null;
            oldSalesOrder.ReceiptCondition = receiptCondition;
            oldSalesOrder.ReceiptType = receiptType;
            oldSalesOrder.Remark = remark;
            oldSalesOrder.SaleDate = saleDate;
            oldSalesOrder.SaleEmployee = saleEmployee;
            oldSalesOrder.WareHouse = wareHouse;

            IList<long> deleted = new List<long>();
            foreach (SalesOrderDetail newSalesOrderDetail in salesOrderDetails)
            {
                if (newSalesOrderDetail.Id == 0)
                {
                    decimal salesAmount = newSalesOrderDetail.Number * newSalesOrderDetail.UnitCost;
                    decimal sodDiscountAmount = salesAmount * newSalesOrderDetail.DiscountRate;
                    decimal taxAmount = newSalesOrderDetail.Number * newSalesOrderDetail.UnitCost * newSalesOrderDetail.TaxRate;
                    decimal wholeAmount = salesAmount + taxAmount - sodDiscountAmount;
                    SalesOrderDetail newsod = SalesModuleFactory.CreateSalesOrderDetail(newSalesOrderDetail.Number, newSalesOrderDetail.UnitCost, newSalesOrderDetail.DiscountRate, sodDiscountAmount,
                        salesAmount, wholeAmount, newSalesOrderDetail.TaxRate, taxAmount, newSalesOrderDetail.Description, DateTime.Now, DateTime.Now, this._employee.UserAccount.Id,
                        oldSalesOrder, newSalesOrderDetail.Stock, newSalesOrderDetail.WareHouse);
                    oldSalesOrder.SalesOrderDetails.Add(newsod);
                    continue;
                }
                if (newSalesOrderDetail.EditUser == -1)
                {
                    deleted.Add(newSalesOrderDetail.Id);
                    continue;
                }
            }
            foreach (SalesOrderDetail oldSalesOrderDetail in oldSalesOrder.SalesOrderDetails)
            {
                foreach (SalesOrderDetail newSalesOrderDetail in salesOrderDetails)
                {
                    if (oldSalesOrderDetail.Id == newSalesOrderDetail.Id)
                    {
                        oldSalesOrderDetail.SalesAmount = newSalesOrderDetail.Number * newSalesOrderDetail.UnitCost;
                        oldSalesOrderDetail.Description = newSalesOrderDetail.Description;
                        oldSalesOrderDetail.DiscountAmount = newSalesOrderDetail.SalesAmount * newSalesOrderDetail.DiscountRate;
                        oldSalesOrderDetail.DiscountRate = newSalesOrderDetail.DiscountRate;
                        oldSalesOrderDetail.Number = newSalesOrderDetail.Number;
                        oldSalesOrderDetail.Stock = newSalesOrderDetail.Stock;
                        oldSalesOrderDetail.TaxAmount = newSalesOrderDetail.SalesAmount * newSalesOrderDetail.TaxRate;
                        oldSalesOrderDetail.TaxRate = newSalesOrderDetail.TaxRate;
                        oldSalesOrderDetail.UnitCost = newSalesOrderDetail.UnitCost;
                        oldSalesOrderDetail.WareHouse = newSalesOrderDetail.WareHouse;
                        oldSalesOrderDetail.WholeAmount = newSalesOrderDetail.SalesAmount + newSalesOrderDetail.TaxAmount - newSalesOrderDetail.DiscountAmount;
                    }
                }
            }

            foreach (long id in deleted)
            {
                SalesOrderDetail deletedSalesOrderDetail = oldSalesOrder.SalesOrderDetails.FirstOrDefault(d => d.Id == id);
                oldSalesOrder.SalesOrderDetails.Remove(deletedSalesOrderDetail);
            }

            result.Object = oldSalesOrder;

            return result;
        }


        public SellProductToCustomerResult SellProductToCustomer(string id, DateTime saleDate, string deliveryCode, string deliveryAddress, string customerOrderCode, string checkCode, int checkType,
            string remark, decimal discountRate, int receiptType, int receiptCondition, decimal thisAmount,
            int payDay, DateTime deadDate, string currency, decimal currencyRate,decimal taxRate,
            Customer customer, Employee salesEmployee, Project project, WareHouse wareHouse, SalesOrder salesOrder, Account receiptAccount, Employee receiptEmployee,
            StockInOut stockInOut, IList<StockInOutDetail> stockInDetails, StockInOutType type)
        {
            SellProductToCustomerResult result = new SellProductToCustomerResult();

            Sale sale = SalesModuleFactory.CreateSale(id, saleDate, deliveryCode, deliveryAddress, customerOrderCode, checkCode, checkType, remark, discountRate,
                        receiptType, receiptCondition, payDay, deadDate, currency, currencyRate, taxRate, DateTime.Now, DateTime.Now, this._employee.UserAccount.Id, 
                        this._employee.Corporation, customer, salesEmployee, project, wareHouse, null, salesOrder, receiptAccount, receiptEmployee);

            decimal receiptAmount = 0;
            decimal discount = 0;
            IList<StockInOutDetail> newStockInDetails = new List<StockInOutDetail>();
            foreach (StockInOutDetail siod in stockInOut.StockInOutDetails)
            {
                receiptAmount +=  siod.Number * siod.UnitCost;
                discount += siod.Number * siod.UnitCost * siod.DiscountRate;
                decimal sodCost = 0;
                //计算成本
                decimal tempLeftNumber = siod.Number;
                foreach (StockInOutDetail sid in stockInDetails.Where(s=>s.Number > 0).OrderBy(s=>s.CreateDate))
                {
                    if (siod.Stock.Id == sid.Stock.Id)
                    {
                        if (sid.LeftNumber > tempLeftNumber)
                        {
                            sid.LeftNumber -= tempLeftNumber;
                            sodCost += (tempLeftNumber * sid.UnitCost) / (1 + (siod.TaxRate / 100));
                            break;
                        }
                        else
                        {
                            tempLeftNumber -= sid.LeftNumber;
                            sid.LeftNumber = 0;
                            sodCost += (sid.LeftNumber * sid.UnitCost) / (1 + (siod.TaxRate / 100));
                            continue;
                        }
                    }
                }

                //生成出库单明细
                StockInOutDetail stockInDetail = StockModuleFactory.CreateStockInDetail(null, siod.Number, siod.UnitCost, StockIdentifier.CostType.StandardPrice,
                   sodCost, siod.TaxRate, siod.DiscountRate, siod.Description, this._employee.UserAccount.Id, siod.Stock, siod.WareHouse);

                newStockInDetails.Add(stockInDetail);

                if (salesOrder.IsNotNull())
                {
                    foreach (SalesOrderDetail sod in salesOrder.SalesOrderDetails)
                    {
                        if (siod.Stock.Id == sod.Stock.Id)
                        {
                            if (sod.UnStockOutNumber >= siod.Number)
                            {
                                //更新销售订单未出库数量
                                sod.UnStockOutNumber = sod.UnStockOutNumber - siod.Number;
                                sod.UnStockOutAmount = sod.UnStockOutAmount - (siod.Number * siod.UnitCost);
                                sod.ModifyDate = DateTime.Now;
                            }
                            else
                            {
                                result.IsSuccess = false;
                                result.Messages.Add("出库数量不得大于销售订单数量");
                                return result;
                            }
                        }
                    }
                }
            }

            StockManager stockManager = new StockManager(this._employee);

            //生成出库单
            BoolGenericResult<StockInOut> addStockInResult = stockManager.AddStockOut(string.Empty, DateTime.Now, type, sale.Id, null, string.Empty, currency, currencyRate,
                remark, string.Empty, string.Empty, null, salesEmployee, project, null, customer, wareHouse, newStockInDetails);
            if (!addStockInResult.IsSuccess)
            {
                result.IsSuccess = false;
                result.Messages.Add(addStockInResult.Messages.FirstOrDefault());
                return result;
            }
            
            Receivables receivables = SalesModuleFactory.CreateReceivables(saleDate, currency, currencyRate, remark,
                discount, receiptAccount, payDay, deadDate, receiptAmount, SalesIdentifier.ReceivablesType.Sales, sale.Id, DateTime.Now, DateTime.Now, this._employee.UserAccount.Id,
                this._employee.Corporation, customer, salesEmployee, project);
            result.OtherReceivables = receivables;

            if (salesOrder.IsNotNull())
            {
                //更新销售订单出库状态
                salesOrder.StockOutStatus = SalesIdentifier.StockOutStatus.AllOut;
                foreach (SalesOrderDetail sod in salesOrder.SalesOrderDetails)
                {
                    if (sod.UnStockOutNumber > 0)
                    {
                        salesOrder.StockOutStatus = SalesIdentifier.StockOutStatus.PartOut;
                        break;
                    }
                }
            }

            result.Object = sale;
            result.Object.StockInOut = addStockInResult.Object;

            return result;
        }


        public SellProductToCustomerResult UpdateProductToCustomer(Sale oldSale, Receivables oldOtherReceivables, DateTime saleDate, string deliveryCode, string deliveryAddress, string customerOrderCode, string checkCode, int checkType,
            string remark, decimal discountRate, int receiptType, int receiptCondition, decimal thisAmount,
            int payDay, DateTime deadDate, string currency, decimal currencyRate, decimal taxRate,
            Customer customer, Employee salesEmployee, Project project, WareHouse wareHouse, SalesOrder salesOrder, Account receiptAccount, Employee receiptEmployee, StockInOut stockInOut)
        {
            SellProductToCustomerResult result = new SellProductToCustomerResult();

            oldSale.ReceiptAccount = receiptAccount;
            oldSale.CheckCode = checkCode;
            oldSale.CheckType = checkType;
            oldSale.Currency = currency;
            oldSale.CurrencyRate = currencyRate;
            oldSale.Customer = customer;
            oldSale.CustomerOrderCode = customerOrderCode;
            oldSale.DeadDate = deadDate;
            oldSale.DeliveryAddress = deliveryAddress;
            oldSale.DeliveryCode = deliveryCode;
            oldSale.DiscountRate = discountRate;
            oldSale.PayDay = payDay;
            oldSale.Project = project;
            oldSale.ReceiptCondition = receiptCondition;
            oldSale.ReceiptEmployee = receiptEmployee;
            oldSale.ReceiptType = receiptType;
            oldSale.Remark = remark;
            oldSale.SaleEmployee = salesEmployee;
            oldSale.SalesDate = saleDate;
            oldSale.SalesOrder = salesOrder;
            oldSale.WareHouse = wareHouse;

            IList<long> deleted = new List<long>();
            decimal receiptAmount = 0;
            decimal discount = 0;
            decimal sodDiscountAmouont = 0;
            decimal sodSalesAmount = 0;
            decimal sodTaxAmount = 0;
            decimal sodCost = 0;
            foreach (StockInOutDetail newStockInOutDetail in stockInOut.StockInOutDetails)
            {
                if (newStockInOutDetail.Id == 0)
                {
                    sodDiscountAmouont = newStockInOutDetail.Number * newStockInOutDetail.UnitCost * newStockInOutDetail.DiscountRate;
                    sodSalesAmount = newStockInOutDetail.Number * newStockInOutDetail.UnitCost;
                    sodTaxAmount = sodSalesAmount - (sodSalesAmount / (1 + (taxRate / 100)));
                    sodCost = sodSalesAmount - sodTaxAmount;
                    receiptAmount += sodCost;
                    discount += sodDiscountAmouont;
                    continue;
                }
                if (newStockInOutDetail.EditUser == -1)
                {
                    deleted.Add(newStockInOutDetail.Id);
                    continue;
                }
            }
            foreach (StockInOutDetail oldStockInOutDetail in oldSale.StockInOut.StockInOutDetails)
            {
                foreach (StockInOutDetail newStockInOutDetail in stockInOut.StockInOutDetails)
                {
                    if (oldStockInOutDetail.Id == newStockInOutDetail.Id)
                    {
                        oldStockInOutDetail.Number = newStockInOutDetail.Number;
                        oldStockInOutDetail.Stock = newStockInOutDetail.Stock;
                        oldStockInOutDetail.UnitCost = newStockInOutDetail.UnitCost;
                        oldStockInOutDetail.WareHouse = newStockInOutDetail.WareHouse;
                        sodDiscountAmouont = newStockInOutDetail.Number * newStockInOutDetail.UnitCost * newStockInOutDetail.DiscountRate;
                        sodSalesAmount = newStockInOutDetail.Number * newStockInOutDetail.UnitCost;
                        sodTaxAmount = sodSalesAmount - (sodSalesAmount / (1 + (taxRate / 100)));
                        sodCost = sodSalesAmount - sodTaxAmount;
                        receiptAmount += sodCost;
                        discount += sodDiscountAmouont;
                    }
                }
            }

            foreach (long id in deleted)
            {
                StockInOutDetail deletedStockInOutDetail = oldSale.StockInOut.StockInOutDetails.FirstOrDefault(d => d.Id == id);
                oldSale.StockInOut.StockInOutDetails.Remove(deletedStockInOutDetail);
            }

            result.Object = oldSale;

            oldOtherReceivables.Amount = receiptAmount;
            oldOtherReceivables.Currency = currency;
            oldOtherReceivables.CurrencyRate = currencyRate;
            oldOtherReceivables.Customer = customer;
            oldOtherReceivables.DeadDate = deadDate;
            oldOtherReceivables.Discount = discount;
            oldOtherReceivables.LeftAmount = receiptAmount;
            oldOtherReceivables.ModifyDate = DateTime.Now;
            oldOtherReceivables.PayDay = payDay;
            oldOtherReceivables.Project = project;
            oldOtherReceivables.ReceiptAccount = receiptAccount;
            oldOtherReceivables.ReceiveEmployee = receiptEmployee;
            oldOtherReceivables.Remark = remark;

            return result;
        }

        public ReturnProductFromCustomerResult ReturnProductFromCustomer(string id, DateTime returnDate, string currency, decimal currencyRate, decimal taxRate,
            string remark, string deliveryAddress, string checkCode, Employee salesEmployee, Project project, Account receiptAccount, 
            Customer customer, WareHouse wareHouse, string contact, string contactTelephone, StockInOut stockInOut, Sale sale, StockInOutType type)
        {
            ReturnProductFromCustomerResult result = new ReturnProductFromCustomerResult();

            SalesReturn salesReturn = SalesModuleFactory.CreateSalesReturn(id, returnDate, string.Empty, currency, currencyRate, taxRate, sale.IsNotNull() ? sale.Id : string.Empty, deliveryAddress,
                checkCode, remark, DateTime.Now, DateTime.Now, this._employee.UserAccount.Id, this._employee.Corporation, customer, salesEmployee, project, wareHouse, contact, contactTelephone);

            decimal receiptAmount = 0;
            decimal discountAmount = 0;
            IList<StockInOutDetail> stockInOutDetails = new List<StockInOutDetail>();
            foreach (StockInOutDetail siod in stockInOut.StockInOutDetails)
            {
                decimal sodDiscountAmouont = siod.Number * siod.UnitCost * siod.DiscountRate;
                decimal sodSalesAmount = siod.Number * siod.UnitCost;
                decimal sodTaxAmount = sodSalesAmount - (sodSalesAmount / (1 + (siod.TaxRate / 100)));
                decimal sodCost = sodSalesAmount - sodTaxAmount;
                receiptAmount += sodCost;
                discountAmount += sodDiscountAmouont;

                //生成入库单明细
                StockInOutDetail stockInDetail = StockModuleFactory.CreateStockInDetail(null, siod.Number, siod.UnitCost, StockIdentifier.CostType.StandardPrice,
                    siod.Cost, siod.TaxRate, siod.DiscountRate, siod.Description, this._employee.UserAccount.Id, siod.Stock, siod.WareHouse);
                stockInOutDetails.Add(stockInDetail);
            }

            StockManager stockManager = new StockManager(this._employee);

            //生成入库单
            BoolGenericResult<StockInOut> addStockInResult = stockManager.AddStockIn(string.Empty, DateTime.Now, type, salesReturn.Id, null, string.Empty, currency, currencyRate,
                remark, contact, contactTelephone, null, salesEmployee, project, null, customer, wareHouse, stockInOutDetails);
            result.Object.StockInOut = addStockInResult.Object;

            Receivables otherReceivables = SalesModuleFactory.CreateReceivables(returnDate, currency, currencyRate, remark,
                discountAmount, receiptAccount, 0, DateTime.Now.AddYears(1), receiptAmount, SalesIdentifier.ReceivablesType.SalesReturn, salesReturn.Id, DateTime.Now, DateTime.Now, this._employee.UserAccount.Id,
                this._employee.Corporation, customer, salesEmployee, project);

            result.OtherReceivables = otherReceivables;
            result.Object = salesReturn;
            
            return result;
        }
    }
}
